﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace Dashboard.Common
{
    /// <summary>
    /// 为了解决PasswordBox无法直接进行双向数据绑定，所以建立了本类作为依赖对象，内含一个静态只读的依赖属性PasswordProperty 
    /// 
    /// 为了使密码框中的密码能够绑定到本类中的PasswordProperty内，需要在LoginView.xaml中引入本类作为命名空间，以此来给PasswordBox添加其本来没有的属性————Attach，命名空间引入形式如下：
    /// xmlns:common="clr-namespace:Dashboard.Common"
    /// 通过下面的代码可以给PasswordBox添加额外属性Attach：
    /// common:PasswordHelper.Attach="True"
    /// 因为添加的额外属性来自C#代码，所以，在完成一次新的编译前，这种引用xaml都是不能解析的
    /// </summary>
    public class PasswordHelper
    {
        ////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////解决Model中变动了密码同步到View中//////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// 定义LoginView中Password的依赖属性：PasswordProperty（相当于就是密码框的“替身”），其中：
        /// DependencyProperty.RegisterAttached方法：使用指定的属性名、属性类型、所有者类型和属性元数据注册附加的属性。
        /// </summary>
        public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached(
            "Password"                                                                                  /// 属性名：Password（PasswordBox控件的Password属性）
            , typeof(string)                                                                            /// 该属性对应的数据类型是：string
            , typeof(PasswordHelper)                                                                    /// 受理该属性双向数据绑定的依赖对象是：PasswordHelper（本类）
            , new FrameworkPropertyMetadata(                                                            /// 该属性发生变动时执行的委托（来自本类定义的OnPropertyChanged方法：即该属性变动时执行OnPropertyChanged方法）
                ""
                , new PropertyChangedCallback(OnPropertyChanged)
            )
        );

        /// <summary>
        /// 从密码框的替身身上拿到密码内容
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public static string GetPassword(DependencyObject d)
        {
            return d.GetValue(PasswordProperty).ToString();
        }
        /// <summary>
        /// 给密码框的替身设置值
        /// </summary>
        /// <param name="d"></param>
        /// <param name="value"></param>
        public static void SetPassword(DependencyObject d,string value)
        {
            d.SetValue(PasswordProperty, value);
        }
        /// <summary>
        /// 当替身中的内容发生变动时触发
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("密码框的替身有变动！");
            PasswordBox passwordBox = d as PasswordBox;
            passwordBox.PasswordChanged -= PasswordBox_PasswordChanged; /// 如果PasswordHelper内部修改了密码框中的密码时，为防止触发成环，故先解除绑定的事件
            /// 若不是PasswordHelper内部进行密码的更改，则将替身的新密码写入到密码框中（通过这个标志位判断可以解决触发成环的问题，当然在尝试修改密码框中的密码之前临时解除密码框变动事件的绑定也可以防止触发成环）
            if (!_isUpdating)
                passwordBox.Password = e.NewValue?.ToString();
            passwordBox.PasswordChanged += PasswordBox_PasswordChanged; /// 完成对密码框中的密码修改后，需加上密码框内容变动事件（否则用户后续修改密码框无法将新的密码同步进来）
        }



        ////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////解决View中变动了密码同步到Model中//////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// 自定义一个依赖属性Attach，其中：
        /// DependencyProperty.RegisterAttached方法：使用指定的属性名、属性类型、所有者类型和属性元数据注册附加的属性。
        /// </summary>
        public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached(
            "Attach"                                                                                    /// 属性名：Attach（这个属性实际上PasswordBox没有，是我们自定义的属性，之后需要在PasswordBox中添加这个属性）
            , typeof(bool)                                                                              /// 该属性对应的数据类型是：bool
            , typeof(PasswordHelper)                                                                    /// 受理该属性双向数据绑定的依赖对象是：PasswordHelper（本类）
            , new FrameworkPropertyMetadata(                                                            /// 该属性发生变动时执行的委托（来自本类定义的OnAttached方法：即该属性变动时执行OnAttached方法）
                false
                , new PropertyChangedCallback(OnAttached)
            )
        );

        /// <summary>
        /// 从AttachProperty拿到值
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public static bool GetAttach(DependencyObject d)
        {
            return (bool)d.GetValue(AttachProperty);
        }
        /// <summary>
        /// 给AttachProperty设置值
        /// </summary>
        /// <param name="d"></param>
        /// <param name="value"></param>
        public static void SetAttach(DependencyObject d, bool value)
        {
            d.SetValue(AttachProperty, value);
        }
        /// <summary>
        /// 当AttachProperty变化时触发
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("密码框有变动！");
            PasswordBox passwordBox = d as PasswordBox;
            // 给密码框绑定密码变动事件
            passwordBox.PasswordChanged += PasswordBox_PasswordChanged;
        }

        /// <summary>
        /// 密码变动事件：
        /// 当密码框中的密码变动时，将新的面貌设置到其替身中
        /// 这会引起触发链：触发方法OnPropertyChanged（替身内容变化时将新内容写到密码框，进而又会触发本事件造成触发器成环，解决办法是通过一个内部标志位isUpdating来控制触发的深度）
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            PasswordBox passwordBox = sender as PasswordBox;

            /// 每次通过PasswordHelper内部进行密码框内容更改时，都标记一下标志位
            _isUpdating = true;
            SetPassword(passwordBox, passwordBox.Password);
            /// 更改完成后还原标志位
            _isUpdating = false;
        }

        /// <summary>
        /// 触发控标识位（默认false：表示当前PasswordHelper内部没有在更改密码框中的密码）
        /// </summary>
        static bool _isUpdating = false;
    }
}
